#include "../include/errors.h"
#include "../include/header.h"
#include "../include/constants.h"
#include "../include/hashtable.h"
#include "../include/binsrchtree.h"
#include "../include/naryTree.h"
#include "../include/general_vfs_op.h"

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
/*
VFS contents:
1. mainheader
2. list of input size
3. write root fd
4. write 99 fds
5. write blocks

1. int create_vfs(char file_name[],unsigned long size)

2. int mount_vfs(char filename[])

3. int unmount_vfs(char filename[])
*/


/*
Function Name: create_vfs(...)
Descriptions: create a file where the vfs data will be stored
Parameters: vfs name and vfs size(in KB)
Return Type: int: successful/unsuccessful and values
    corresponding to other errors.
*/

int create_vfs(char file_name[],int size)
{
    unsigned long size_copy,meta_size,offset_val;
    int i=0,block_counter=0;
    int num;
    unsigned int size_fd,size_list;
    char *buff;
    char *temp_check=NULL;
    FILE *fp;
    fdesc *assign_fd;
    freelist *assign_list;
    mainheader *vfs_read;
    freelist *list_read;
    fdesc *fdes_read;

    if(if_valid_args_(file_name));
    else
        return 0;
    if(strlen(file_name)>30)
        return 7;
    if(!validate_name(file_name))
        return 4;
    if(size<1 || size>1024)
        return 5;
    fp=fopen(file_name,"rb");
    if(fp!=NULL)
    {
        fclose(fp);
        return 1;
    }
    fp=fopen(file_name,"wb");
    if(fp==NULL)
    {
        return 2;
    }
    else
    {
        assign_fd=(fdesc*)malloc(sizeof(fdesc));
        assign_list=(freelist*)malloc(sizeof(freelist));
        vfs_read=(mainheader*)malloc(sizeof(mainheader));
        list_read=(freelist*)malloc(sizeof(freelist));
        fdes_read=(fdesc*)malloc(sizeof(fdesc));
        buff=(char*)malloc(sizeof(char)*BLOCKSIZE);

        size_list=sizeof(freelist);
        size_fd=sizeof(fdesc);
        size*=1024;

        vfs_read->num_blocks=size/BLOCKSIZE;
        vfs_read->size=size;
        num=vfs_read->num_blocks;
        vfs_read->max_fd=MAXFD;
        vfs_read->f_usedfDes=1;
        temp_check=(char*)malloc(sizeof(mainheader)+
                                 size_list*vfs_read->num_blocks+size_fd*vfs_read->max_fd);
#if OPER_DEBUG
        printf("%d temp\n",sizeof(mainheader)+size_list*vfs_read->num_blocks+
               size_fd*vfs_read->max_fd);
#endif
        if(temp_check==NULL)
        {
            return 2;
        }
        free(temp_check);
        size_copy=size;
        offset_val=sizeof(mainheader);
        strcpy(vfs_read->f_label,file_name);
        vfs_read->fList=offset_val;
        vfs_read->fDes=offset_val+(size_list*vfs_read->num_blocks);
        vfs_read->fBlock=offset_val+(size_list*vfs_read->num_blocks)+
                         (size_fd*vfs_read->max_fd);
#if OPER_DEBUG
        printf("num_blocks in : %d, size: %lu\n",vfs_read->num_blocks,vfs_read->size);
        printf("to file: \nl:%s\tmax:%d\tused:%d\tlist: %lu\tfd: %lu block%lu\n",
               vfs_read->f_label, vfs_read->max_fd, vfs_read->f_usedfDes,
               vfs_read->fList, vfs_read->fDes, vfs_read->fBlock);
        printf("initial fp: %ld\n",ftell(fp));
#endif

        /*
        write main header from the global structure to vfs
        */
        fwrite(vfs_read,sizeof(mainheader),1,fp);
#if OPER_DEBUG
        printf("after writing header: %ld\n",ftell(fp));
#endif

        /*
        write list:
        1-> init global structure's list structure
        2-> write list of len size to vfs
        */
        list_read->f_bnumber=vfs_read->fBlock;
        list_read->f_bfree=0;
        fwrite(list_read,size_list,1,fp);
#if OPER_DEBUG
        printf("offset %ld\n main bnum: %lu\t main bfree: %lu\n",offset_val,
               list_read->f_bnumber,list_read->f_bfree);
#endif
        for(i=1; i<vfs_read->num_blocks; i++)
        {
            assign_list->f_bnumber=vfs_read->fBlock+i*BLOCKSIZE;
#if OPER_DEBUG
            printf("%ld\n",offset_val+(vfs_read->num_blocks*(size_list))+
                   (vfs_read->max_fd*(size_fd))+i*BLOCKSIZE);
#endif
            assign_list->f_bfree=0;
            fwrite(assign_list,size_list,1,fp);
        }
#if OPER_DEBUG
        printf("list write done: %ld\n",ftell(fp));
#endif

        /*
        write descriptors:
        1-> init global structure's descriptor with mount point
        2-> write 100 descriptors;
        */
        strcpy(fdes_read->f_name,"/");
        strcpy(fdes_read->f_path,"");
        strcpy(fdes_read->f_type,"DIR");
        fdes_read->block_number=-1;
        fdes_read->f_size=0;
        fwrite(fdes_read,size_fd,1,fp);
        for(i=0; i<vfs_read->max_fd-1; i++)
        {
#if OPER_DEBUG
            printf("%ld\n",ftell(fp));
#endif
            fwrite(fdes_read,size_fd,1,fp);
        }
#if OPER_DEBUG
        printf("fd write done: %ld\n",ftell(fp));
#endif

        /*
        create blocks
        */
        memset(buff,'0',BLOCKSIZE);
        while(num>0)
        {
            fwrite(buff,sizeof(buff),1,fp);
            num--;
        }
#if OPER_DEBUG
        printf("after blocks creation: in file at %ld\n",ftell(fp));
        printf("size: header:%d fd:%d fl:%d fp:%ld\n",sizeof(mainheader),
               sizeof(fdesc),sizeof(freelist),ftell(fp));
#endif
        fclose(fp);
        return 10;
    }
}

/*
Function Name: mount_vfs(...)
Descriptions: read the vfs data from the file created using create_vfs and
    populate all data structures with the values in vfs file
Parameters: vfs file name
Return Type: int: successful/unsuccessful and values corresponding to other errors.
*/
int mount_vfs(char filename[])
{
    int i=0,pop_ret=-1,ret=0;
    hashtable *ht=NULL;
    b_node *bst=NULL;
    n_tree *n_root=NULL;
    l_node *list=NULL;
    mainheader *vfs_read;
    freelist *assign_list;
    fdesc *assign_fd;
    writeBlock *read;
    extern fdesc **send_fd;
    extern mainStr *mainstr;
    extern dataStr *datastr;
    extern unsigned long **listarr;

    if(if_valid_args_(filename));
    else
        return 0;
    if(mainstr!=NULL)
    {
        if(mainstr->is_mounted==5 &&
                strcmp(filename,mainstr->vfs_read->f_label)==0)
            return 4;
        if(mainstr->is_mounted==5 &&
                strcmp(filename,mainstr->vfs_read->f_label)!=0)
            return 2;
    }
    mainstr=(mainStr*)malloc(sizeof(mainStr));
    mainstr->mountpoint=(FILE*)malloc(sizeof(FILE));
    datastr=(dataStr*)malloc(sizeof(dataStr));
    read=(writeBlock*)malloc(sizeof(writeBlock));
    mainstr->mountpoint=fopen(filename,"rb");

#if OPER_DEBUG
    printf("check mount %d\n",mainstr->is_mounted);
#endif
    if(mainstr->mountpoint==NULL)
    {
        return 1;
    }
    fclose(mainstr->mountpoint);
    mainstr->mountpoint=fopen(filename,"rb+");
#if OPER_DEBUG
    printf("2 %p",mainstr->mountpoint);
#endif

    vfs_read=(mainheader*)malloc(sizeof(mainheader));
    assign_list=(freelist*)malloc(sizeof(freelist));
    assign_fd=(fdesc*)malloc(sizeof(fdesc));
    send_fd=(fdesc**)malloc(sizeof(fdesc*)*MAXFD);

    for(i=0; i<MAXFD; i++)
    {
        send_fd[i]=(fdesc*)malloc(sizeof(fdesc));
    }

    datastr=(dataStr*)malloc(sizeof(dataStr));

#if OPER_DEBUG
    printf("mount main %p",mainstr->mountpoint);
    printf("sizeof vfs_read %d \n",sizeof(vfs_read));
#endif
    if(!fread(vfs_read,sizeof(mainheader),1,mainstr->mountpoint))
    {
        fclose(mainstr->mountpoint);
        return 2;
    }
    mainstr->vfs_read=vfs_read;
    mainstr->num_used_fd=vfs_read->f_usedfDes;
    listarr=(unsigned long **)malloc(sizeof(unsigned long *)*vfs_read->size);

    for(i=0; i<vfs_read->num_blocks; i++)
    {
        listarr[i]=(unsigned long *)malloc(sizeof(unsigned long));
    }
#if OPER_DEBUG
    printf("read vfs\n");
    printf("after reading header: %ld\n",ftell(mainstr->mountpoint));
    printf("from file: %s\t%d\t%d\t%ld\t%ld\t %d\t%ld \n",vfs_read->f_label,
           vfs_read->max_fd, vfs_read->f_usedfDes, vfs_read->fList,
           vfs_read->fDes,vfs_read->num_blocks, vfs_read->fBlock );
    printf("from file mount : %s\t%d\t%d\t%ld\t%ld\t %d\t%ld \n",
           mainstr->vfs_read->f_label, mainstr->vfs_read->max_fd,
           mainstr->vfs_read->f_usedfDes, mainstr->vfs_read->fList,
           mainstr->vfs_read->fDes, mainstr->vfs_read->num_blocks,
           mainstr->vfs_read->fBlock);
    printf("in mount\n");
    printf("num_blocks in vfs_read: %d, size: %lu fd: %d\n",
           vfs_read->num_blocks, vfs_read->size,vfs_read->max_fd);
#endif

    /*
    1-> allocate mem for DS
    2-> assign root values
    */
    ht=(hashtable *)malloc(sizeof(hashtable));
    bst=(struct bst_node*)malloc(sizeof(struct bst_node));
    bst->key=(char*)malloc(sizeof(char)*CHARSIZE);
    n_root=(n_tree*)malloc(sizeof(n_tree));
    datastr->bst=bst;
    datastr->htable=ht;
    datastr->ntree=n_root;

    /*
    reading list
    */
    fread(assign_list,sizeof(freelist),1,mainstr->mountpoint);
    mainstr->list_read=assign_list;
    listarr[0][0]=assign_list->f_bnumber;
    listarr[0][1]=assign_list->f_bfree;
#if OPER_DEBUG
    printf("list %ld\t%ld\n",assign_list->f_bnumber, assign_list->f_bfree);
#endif
    for(i=1; i<vfs_read->num_blocks; i++)
    {
#if OPER_DEBUG
        printf("list read vals: %ld\n",ftell(mainstr->mountpoint));
#endif
        fread(assign_list,sizeof(freelist),1,mainstr->mountpoint);
        listarr[i][0]=assign_list->f_bnumber;
        listarr[i][1]=assign_list->f_bfree;
#if OPER_DEBUG
        printf("list %ld\t%ld\n",assign_list->f_bnumber, assign_list->f_bfree);
#endif
    }

    /*
    read filedescriptors
    1-> root FD
    2-> rest of the descriptors
    */
#if OPER_DEBUG
    printf("after list read: %ld\n",ftell(mainstr->mountpoint));
#endif
    fread(send_fd[0],sizeof(fdesc),1,mainstr->mountpoint);
    mainstr->fdes_read=assign_fd;
#if OPER_DEBUG
    printf("assign fd n:%s\tp:%s\tt:%s\t%lu\t%ld\n",assign_fd->f_name,
           assign_fd->f_path, assign_fd->f_type, assign_fd->f_size,
           assign_fd->block_number);
    printf("mainstr fd n:%s\tp:%s\tt:%s\t%lu\t%ld\n",
           mainstr->fdes_read->f_name, mainstr->fdes_read->f_path,
           mainstr->fdes_read->f_type, mainstr->fdes_read->f_size,
           mainstr->fdes_read->block_number);
#endif
    initialize_hashtable(ht);
    init_root(n_root,send_fd[0]);
    ret=init_bst(bst,send_fd[0]);
#if OPER_DEBUG
    printf("root fd n:%s\tp:%s\tt:%s\t%lu\t%ld\n",assign_fd->f_name,
           assign_fd->f_path, assign_fd->f_type, assign_fd->f_size,
           assign_fd->block_number);
    printf("after filedes read: %ld\n",ftell(mainstr->mountpoint));
    printf("bef 2nd fd is read  -%s-%s-%s-%s\n",(bst)->fileDes->f_path,
           (bst)->fileDes->f_name,(bst)->fileDes->f_type,(bst)->key);
    printf("%d max fd %d used\n",vfs_read->max_fd,vfs_read->f_usedfDes);
#endif
    for(i=1; i<mainstr->num_used_fd; i++)
    {
        fread(send_fd[i],sizeof(fdesc),1,mainstr->mountpoint);
#if OPER_DEBUG
        printf("inp  send -%s-%s-%s-\n",send_fd[i]->f_path,send_fd[i]->f_name,
               send_fd[i]->f_type);
#endif
        pop_ret=populate(ht,n_root,bst,send_fd[i]);
#if OPER_DEBUG
        printf("assign_fd n:%s\tp:%s\tt:%s\t%ld\t%ld\n",assign_fd->f_name,
               assign_fd->f_path, assign_fd->f_type, assign_fd->f_size,
               assign_fd->block_number);
#endif
    }
    mainstr->vfs_read->f_usedfDes=mainstr->num_used_fd;
#if OPER_DEBUG
    for(i=0; i<vfs_read->num_blocks; i++)
    {
        if(listarr[i][1]==1)
        {
            fseek(mainstr->mountpoint,listarr[i][0],0);
            fread(read,sizeof(writeBlock),1,mainstr->mountpoint);
            printf("block data %d BEGIN \n %s \nEND\n",i,read->f_buffer);
        }
    }
    printf("after filedes read: %ld\n",ftell(mainstr->mountpoint));
#endif
    mainstr->is_mounted=5;
#if OPER_DEBUG
    printf("aft mount %d\n",mainstr->is_mounted);
#endif
    return 10;
}


/*
Function Name: unmount_vfs(...)
Descriptions: write all data from data structures into vfs file
Parameters: vfs name
Return Type: int: successful/unsuccessful and values
    corresponding to other errors.
*/
int unmount_vfs(char filename[])
{
    freelist *assign_list;
    int i=0,trav_ret=-1;
    extern mainStr *mainstr;
    extern dataStr *datastr;
    extern fdesc **send_fd;
    extern unsigned long **listarr;

    if(if_valid_args_(filename));
    else
        return 0;
    if(mainstr==NULL || mainstr->is_mounted!=5)
        return 4;
    else if(strcmp(filename,mainstr->vfs_read->f_label)==0 &&
            mainstr->is_mounted!=5)
        return 4;
    else if(strcmp(filename,mainstr->vfs_read->f_label)!=0)
    {
        return 2;
    }
    else
    {
        assign_list=(freelist*)malloc(sizeof(freelist));
        rewind(mainstr->mountpoint);
#if OPER_DEBUG
        printf("%d %s file %s\n",mainstr->is_mounted,
               mainstr->vfs_read->f_label,filename);
        printf("%p \n",mainstr);
#endif
        if(if_valid_args_(filename));
        else
            return 0;
#if OPER_DEBUG
        printf("%ld rew\n",ftell(mainstr->mountpoint));
        printf("label:%s maxfd:%d usedfd:%d num_bl:%d list:%ld fDes:%ld\n",
               mainstr->vfs_read->f_label, mainstr->vfs_read->max_fd,
               mainstr->vfs_read->f_usedfDes, mainstr->vfs_read->num_blocks,
               mainstr->vfs_read->fList, mainstr->vfs_read->fDes);
        printf("bst: %p %s\n",datastr->bst,datastr->bst->key);
        printf("bef head write %ld mainheader %d \n",
               ftell(mainstr->mountpoint),sizeof(mainstr->vfs_read));
#endif
        if(fwrite(mainstr->vfs_read,sizeof(mainheader),1,
                  mainstr->mountpoint)!=1)
            return 3;
#if OPER_DEBUG
        printf("after writing header: %ld\n",ftell(mainstr->mountpoint));
#endif
        for(i=0; i<mainstr->vfs_read->num_blocks; i++)
        {
            assign_list->f_bnumber=listarr[i][0];
            assign_list->f_bfree=listarr[i][1];
            fwrite(assign_list,sizeof(freelist),1,mainstr->mountpoint);
        }
#if OPER_DEBUG
        printf(" assign: %ld\n",ftell(mainstr->mountpoint));
        printf("aft list write %ld\n",ftell(mainstr->mountpoint));
#endif
        trav_ret=unmount_travesal(datastr->ntree);
        for(i=0; i<mainstr->vfs_read->f_usedfDes; i++)
        {
            fwrite(send_fd[i],sizeof(fdesc),1,mainstr->mountpoint);
        }
#if OPER_DEBUG
        printf("aft trav %ld\n",ftell(mainstr->mountpoint));
        printf("return from trav\n");
#endif
        mainstr->is_mounted=-1;
        fclose(mainstr->mountpoint);
#if OPER_DEBUG
        printf("aft unmount %d\n",mainstr->is_mounted);
#endif
        free(mainstr);
        mainstr=NULL;
        return 10;
    }
    return 5;
}

